package edu.mtu.citizenscience.ethnography;


import java.io.File;
import java.io.IOException;
import java.util.Calendar;
import java.util.Date;
import java.util.Locale;

import edu.mtu.citizenscience.ethnography.database.Interview;
import edu.mtu.citizenscience.ethnography.database.InterviewAudio;
import edu.mtu.citizenscience.ethnography.database.InterviewPhoto;
import edu.mtu.citizenscience.ethnography.util.DataServiceActivity;
import edu.mtu.citizenscience.ethnography.util.Util;
import edu.mtu.citizenscience.gpsservice.CSGpsService;
import edu.mtu.citizenscience.gpsservice.CSLocationListener;
import edu.mtu.citizenscience.gpsservice.CSLocationListenerBroadcastHandler;
import edu.mtu.citizenscience.gpsservice.CSLocationListenerConnection;
import edu.mtu.citizenscience.gpsservice.CSLocationListenerContainer;
import edu.mtu.citizenscience.gpsservice.CSLocationListenerReceiver;

import android.app.AlertDialog;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.DialogInterface;
import android.content.DialogInterface.OnClickListener;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.ServiceConnection;
import android.content.SharedPreferences;
import android.location.Location;
import android.media.MediaRecorder;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.preference.PreferenceManager;
import android.provider.MediaStore;
import android.util.Log;
import android.view.View;
import android.widget.Button;

/**
 * Activity responsible for starting and stopping audio recording.
 * Can also take pictures.
 * @author john
 *
 */
public class RecordInterviewActivity extends DataServiceActivity implements CSLocationListenerContainer {

	// Audio recording related variables
	private MediaRecorder mRecorder = null;
	private boolean recording = false;
	private InterviewAudio currentAudio = null;
	private InterviewPhoto currentPhoto = null;
	boolean takingPhoto = false;

	// Global activity variables
	private Interview currentInterview = null;
	private int currentInterviewID = -1;


	//Boilerplate log tag
	private static final String LOG_TAG = "RecordInterviewActivity";

	// GPS service
	private CSLocationListener gps;

	// Something to hold the AlertDialog so it can be dismissed
	private AlertDialog dialog;
	private BroadcastReceiver receiver;

	private ServiceConnection gpsConnection;

	private static final int MAX_RECORD_LEN = 1000 * 60 * 30; // 30 minutes
	
	private Button recordButton;

	private Handler handler;
	private Runnable splitRecording = new Runnable() {

		@Override
		public void run() {
			try {
				stopRecording();

				getService().saveAudioFile(currentAudio);
				currentAudio = getService().newAudioForInterview(currentInterview);

				beginRecording();

				handler.postDelayed(splitRecording, MAX_RECORD_LEN);
			} catch (IOException e) {
				fail(R.string.io_error);
			}
		}
	};
	
	private long startTime;
	private Runnable updateTime = new Runnable() {
		
		@Override
		public void run() {
			long elapsedTime = System.currentTimeMillis() - startTime;
			String timeStr;
			if (Util.isOverOneHour(elapsedTime))  {
				timeStr = Util.formatTimeWithHours(elapsedTime);
			} else {
				timeStr = Util.formatTimeNoHours(elapsedTime);
			}
			String buttonStr = String.format(Locale.US, "%s\n\n%s", getString(R.string.record_interview_audio_button_started), timeStr);
			recordButton.setText(buttonStr);
			handler.postDelayed(updateTime, TIME_UPDATE_LEN);
		}
	};
	private static final int TIME_UPDATE_LEN = 1000; // one second

	/**
	 * Loads the layout on activity creation.  Nothing special here
	 * 
	 */
	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		Log.d(LOG_TAG, LOG_TAG + " has been created");
		setContentView(R.layout.activity_record_interview);

		if (savedInstanceState == null) {
			Bundle extras = getIntent().getExtras();
			if ( extras != null )
				currentInterviewID = extras.getInt(Util.ID_KEY, -1);
			else {
				Log.e(LOG_TAG, "Missing interviewID in extras");
				finish();
			}
		} else {
			currentInterviewID = savedInstanceState.getInt(Util.ID_KEY, -1);
		}
		if (currentInterviewID == -1) {
			Log.e(LOG_TAG, "Missing interviewID in extras");
			finish();
		}
		handler = new Handler();
		recordButton = (Button) findViewById(R.id.RecordInterviewAudioButton);
	}

	/**
	 * onStart, pulls the current interview ID from the intent extras.
	 * Cannot create a new interview here, as the database connection
	 * isn't resolved until the function finishes.
	 */
	@Override
	protected void onStart(){
		super.onStart();
		Log.d(LOG_TAG, LOG_TAG + " has started"); 

		// Get the GPS service
		gpsConnection = new CSLocationListenerConnection(this);
		Intent intent = new Intent(this, CSGpsService.class);
		bindService(intent, gpsConnection, Context.BIND_AUTO_CREATE);
	}

	@Override
	protected void performDatabaseOperation() {
		currentInterview = getService().getInterview(currentInterviewID);
	}

	/**
	 * Pause method, makes sure to stop using the microphone and
	 * save the current audio file.
	 * 
	 */
	@Override
	protected void onPause(){
		super.onPause();
		Intent e = new Intent();
		e.putExtra("interviewID",currentInterviewID);

		Log.d(LOG_TAG, LOG_TAG + " has paused");

	}

	@Override
	public void onBackPressed() {
		// make sure to release the recording device
		if (recording == true){
			handler.removeCallbacks(splitRecording);
			stopRecording();
		}
		super.onBackPressed();
	}
	/**
	 * Stop method, makes sure to stop using the microphone and
	 * save the current audio file.
	 * 
	 */
	@Override
	protected void onStop(){
		super.onStop();
		checkAudioAndCleanup();
		if (!takingPhoto) {
			checkPhotoAndCleanup();
			if (recording == true) {
				handler.removeCallbacks(splitRecording);
				stopRecording();
			}
		}
		if (receiver != null) {
			unregisterReceiver(receiver);
			receiver = null;
		}
		unbindService(gpsConnection);
		Log.d(LOG_TAG, LOG_TAG + " has stopped");

	}

	/**
	 * Destroy method, makes sure to stop using the microphone and
	 * save the current audio file, then unbind from the database.
	 * 
	 */
	@Override
	protected void onDestroy() {

		Log.d(LOG_TAG, LOG_TAG + " is being destroyed.");
		// make sure to release the recording device
		if (recording == true) {
			handler.removeCallbacks(splitRecording);
			stopRecording();
		}

		super.onDestroy();
	}

	@Override
	protected void onSaveInstanceState(Bundle outState) {
		outState.putInt(Util.ID_KEY, currentInterviewID);
		super.onSaveInstanceState(outState);
	}


	/**
	 * Callback method, called from the layout, begins the audio recording.
	 * Sets the audio state boolean recording depending on if it is stopping or starting
	 * recording. Changes the text on the main recording button to reflect
	 * the state of recording.
	 * 
	 * @param v needed for the onClick method to work
	 */
	public void recordInterview(View v) {
		//if not recording, start recording, otherwise stop recording
		if( !recording ) {
			checkAudioAndCleanup();
			try {
				currentAudio = getService().newAudioForInterview(currentInterview);
			} catch (IOException e1) {
				fail(R.string.io_error);
			}

			SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(getApplicationContext());

			if (currentAudio != null) {
				Location location = gps.getCurrentLocation();
				if (prefs.getBoolean("audio_location_wait", false) && location == null) {
					// No location, want to wait for it
					AlertDialog.Builder builder = new AlertDialog.Builder(this);
					builder.setTitle(R.string.waiting_for_location_title);
					builder.setMessage(R.string.waiting_for_location_start);
					builder.setPositiveButton(R.string.skip_button, new OnClickListener() {

						@Override
						public void onClick(DialogInterface dialog, int which) {
							beginRecording();
							handler.postDelayed(splitRecording, MAX_RECORD_LEN);
							startTime = System.currentTimeMillis();
							handler.post(updateTime);
							RecordInterviewActivity.this.dialog = null;
						}
					});

					dialog = builder.create();
					dialog.show();

					IntentFilter intentFilter = new IntentFilter(CSGpsService.LOCATION_SERVICE);
					receiver = new CSLocationListenerReceiver(new CSLocationListenerBroadcastHandler() {

						@Override
						public void onCSLocationListenerBroadcast(Context context, Intent intent) {
							dialog.dismiss();
							if (!recording)
							{
								beginRecording();
								handler.postDelayed(splitRecording, MAX_RECORD_LEN);
								startTime = System.currentTimeMillis();
								handler.post(updateTime);
							}
						}
					});
					registerReceiver(receiver, intentFilter);
				} else {
					// Have a location, so let's go
					beginRecording();
					handler.postDelayed(splitRecording, MAX_RECORD_LEN);
					startTime = System.currentTimeMillis();
					handler.post(updateTime);
				}
			}
		}
		else {
			handler.removeCallbacks(splitRecording);
			handler.removeCallbacks(updateTime);
			stopRecording();
		}
	}

	private void beginRecording() {
		dispatchRecordAudioIntent();
		setRecordingLabel();
	}

	private void stopRecording() {
		Location location = gps.getCurrentLocation();
		currentAudio.setEndLocation(location);
		if (isBound()) {
			getService().saveAudioFile(currentAudio);
		}
		mRecorder.stop();
		//Reset gets rid of the "unhandled events" errors
		mRecorder.reset();
		mRecorder.release();
		mRecorder = null;
		recording = false;
		setRecordingLabel();
	}

	/**
	 * Method which begins capturing the mic input.
	 * Named as such to match the picture-taking method.
	 */
	private void dispatchRecordAudioIntent(){

		if (currentAudio != null) {
			//mark that recording has actually started
			recording = true;

			Location location = gps.getCurrentLocation();
			currentAudio.setStartLocation(location);
			//grab the file from the database created audio entry
			File audiofile = currentAudio.getAudioFile();

			//initialize the microphone
			mRecorder = new MediaRecorder();
			mRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
			mRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4);
			try {
				//use the file to tell mRecorder what the database already knows
				mRecorder.setOutputFile(audiofile.getCanonicalPath());
			} catch (IllegalStateException e1) {
				fail(R.string.unknown_error);
			} catch (IOException e1) {
				fail(R.string.io_error);
			}
			mRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB);

			try {
				mRecorder.prepare();
			} catch (IOException e) {
				Log.e(LOG_TAG, "prepare() failed");
				fail(R.string.unknown_error);
			}

			//start recording
			mRecorder.start();

			// get the date/time
			Date currentTime = Calendar.getInstance().getTime();
			currentAudio.setDate(currentTime);
			getService().saveAudioFile(currentAudio);


		}
	}


	/**
	 * Picture taking callback function.  Only initializes the database entry for a new
	 * picture, hands the path to the camera application, which returns the photo.
	 * 
	 * 
	 * @param v needed for onClick callback to work
	 */
	public void takePicture(View v) {

		SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(getApplicationContext());


		Location location = gps.getCurrentLocation();
		if (prefs.getBoolean("photo_location_wait", false) && location == null) {
			// No location, want to wait for it
			AlertDialog.Builder builder = new AlertDialog.Builder(this);
			builder.setTitle(R.string.waiting_for_location_title);
			builder.setMessage(R.string.waiting_for_location_start);
			builder.setPositiveButton(R.string.skip_button, new OnClickListener() {

				@Override
				public void onClick(DialogInterface dialog, int which) {
					dispatchTakePictureIntent(0);
				}
			});

			dialog = builder.create();
			dialog.show();

			IntentFilter intentFilter = new IntentFilter(CSGpsService.LOCATION_SERVICE);
			receiver = new CSLocationListenerReceiver(new CSLocationListenerBroadcastHandler() {

				@Override
				public void onCSLocationListenerBroadcast(Context context, Intent intent) {
					dialog.dismiss();
					dispatchTakePictureIntent(0);
				}
			});
			registerReceiver(receiver, intentFilter);
		} else {
			// Have a location, so let's go
			dispatchTakePictureIntent(0);
		}


	}

	private void dispatchTakePictureIntent(int actionCode) {
		Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);

		checkPhotoAndCleanup();
		InterviewPhoto photo = null;
		try {


			photo = getService().newPhotoForInterview(currentInterview);
		} catch (IOException e1) {
			fail(R.string.io_error);
		}

		photo.setLocation(gps.getCurrentLocation());

		File photoFile = photo.getPhotoFile();

		takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(photoFile));

		currentPhoto = photo;
		Date currentTime = Calendar.getInstance().getTime();
		currentPhoto.setDate(currentTime);
		getService().savePhotoFile(photo);

		takingPhoto = true;
		startActivityForResult(takePictureIntent, actionCode);


	}




	/** 
	 * Just a helper function to make make changing the recording button label more compact.
	 * Changes to "Stop Recording" if the recording state is true, and "Start Recording" if false.
	 * 
	 */
	private void setRecordingLabel(){
		if( recording != true ){
			recordButton.setText(R.string.record_interview_audio_button_stopped);
		}

	}

	@Override
	public CSLocationListener getCSLocationListener() {
		return gps;
	}

	@Override
	public void setCSLocationListner(CSLocationListener locationListener) {
		gps = locationListener;
	}

	/**
	 * Checks if the file associated with the currentAudio object exists, and deletes
	 * it from the database if it doesn't. Should be called in onStop() and before a new
	 * Interview is created.
	 */
	private void checkAudioAndCleanup() {
		if (currentAudio != null && !currentAudio.getAudioFile().exists()) {
			try {
				getService().removeAudioFile(currentAudio);
			} catch (IOException e) {
				fail(R.string.io_error);
			}
		}
	}

	private void checkPhotoAndCleanup() {
		if (currentPhoto != null && !currentPhoto.getPhotoFile().exists()) {
			try {
				getService().removePhotoFile(currentPhoto);
			} catch (IOException e) {
				fail(R.string.io_error);
			}
		}
	}


}



